/*
 *  	Copyright (C) 2005-2008 Team XBMC
 *  	http://www.xbmc.org
 *  	Copyright (C) 2008-2009 Andrej Stepanchuk
 *  	Copyright (C) 2009-2010 Howard Chu
 *
 *  This file is part of librtmp.
 *
 *  librtmp is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU Lesser General Public License as
 *  published by the Free Software Foundation; either version 2.1,
 *  or (at your option) any later version.
 *
 *  librtmp is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU Lesser General Public License
 *  along with librtmp see the file COPYING.  If not, write to
 *  the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
 *  Boston, MA  02110-1301, USA.
 *  http://www.gnu.org/copyleft/lgpl.html
 */

#include "../stdafx.h"

#include <string.h>
#include <assert.h>
#include <stdlib.h>

#include "rtmp_sys.h"
#include "amf.h"
#include "log.h"
#include "bytes.h"

static const AMFObjectProperty AMFProp_Invalid={{0, 0},AMF_INVALID};
static const AVal AV_empty={0,0};

/* Data is Big-Endian */
unsigned short
AMF_DecodeInt16(const char *data)
{
	unsigned char*c=(unsigned char*)data;
	unsigned short val;
	val=(c[0]<<8)|c[1];
	return val;
}

unsigned int
AMF_DecodeInt24(const char *data)
{
	unsigned char*c=(unsigned char*)data;
	unsigned int val;
	val=(c[0]<<16)|(c[1]<<8)|c[2];
	return val;
}

unsigned int
AMF_DecodeInt32(const char *data)
{
	unsigned char*c=(unsigned char*)data;
	unsigned int val;
	val=(c[0]<<24)|(c[1]<<16)|(c[2]<<8)|c[3];
	return val;
}

void
AMF_DecodeString(const char *data,AVal *bv)
{
	bv->av_len=AMF_DecodeInt16(data);
	bv->av_val=(bv->av_len>0)?(char*)data+2:NULL;
}

void
AMF_DecodeLongString(const char *data,AVal *bv)
{
	bv->av_len=AMF_DecodeInt32(data);
	bv->av_val=(bv->av_len>0)?(char*)data+4:NULL;
}

double
AMF_DecodeNumber(const char *data)
{
	double dVal;
#if __FLOAT_WORD_ORDER == __BYTE_ORDER
#if __BYTE_ORDER == __BIG_ENDIAN
	memcpy(&dVal,data,8);
#elif __BYTE_ORDER == __LITTLE_ENDIAN
	unsigned char*ci,*co;
	ci=(unsigned char*)data;
	co=(unsigned char*)&dVal;
	co[0]=ci[7];
	co[1]=ci[6];
	co[2]=ci[5];
	co[3]=ci[4];
	co[4]=ci[3];
	co[5]=ci[2];
	co[6]=ci[1];
	co[7]=ci[0];
#endif
#else
#if __BYTE_ORDER == __LITTLE_ENDIAN	/* __FLOAT_WORD_ORER == __BIG_ENDIAN */
	unsigned char*ci,*co;
	ci=(unsigned char*)data;
	co=(unsigned char*)&dVal;
	co[0]=ci[3];
	co[1]=ci[2];
	co[2]=ci[1];
	co[3]=ci[0];
	co[4]=ci[7];
	co[5]=ci[6];
	co[6]=ci[5];
	co[7]=ci[4];
#else /* __BYTE_ORDER == __BIG_ENDIAN && __FLOAT_WORD_ORER == __LITTLE_ENDIAN */
	unsigned char*ci,*co;
	ci=(unsigned char*)data;
	co=(unsigned char*)&dVal;
	co[0]=ci[4];
	co[1]=ci[5];
	co[2]=ci[6];
	co[3]=ci[7];
	co[4]=ci[0];
	co[5]=ci[1];
	co[6]=ci[2];
	co[7]=ci[3];
#endif
#endif
	return dVal;
}

int
AMF_DecodeBoolean(const char *data)
{
	return *data!=0;
}

char*
AMF_EncodeInt16(char *output,char *outend,short nVal)
{
	if(output+2>outend)
		return NULL;

	output[1]=nVal&0xff;
	output[0]=nVal>>8;
	return output+2;
}

char*
AMF_EncodeInt24(char *output,char *outend,int nVal)
{
	if(output+3>outend)
		return NULL;

	output[2]=nVal&0xff;
	output[1]=nVal>>8;
	output[0]=nVal>>16;
	return output+3;
}

char*
AMF_EncodeInt32(char *output,char *outend,int nVal)
{
	if(output+4>outend)
		return NULL;

	output[3]=nVal&0xff;
	output[2]=nVal>>8;
	output[1]=nVal>>16;
	output[0]=nVal>>24;
	return output+4;
}

char*
AMF_EncodeString(char *output,char *outend,const AVal *bv)
{
	if((bv->av_len <65536&&output+1+2+bv->av_len> outend)||output+1+4+bv->av_len>outend)
		return NULL;

	if(bv->av_len<65536)
	{
		*output++=AMF_STRING;

		output=AMF_EncodeInt16(output,outend,bv->av_len);
	}
	else
	{
		*output++=AMF_LONG_STRING;

		output=AMF_EncodeInt32(output,outend,bv->av_len);
	}
	memcpy(output,bv->av_val,bv->av_len);
	output+=bv->av_len;

	return output;
}

char*
AMF_EncodeNumber(char *output,char *outend,double dVal)
{
	if(output+1+8>outend)
		return NULL;

	*output++=AMF_NUMBER;	/* type: Number */

#if __FLOAT_WORD_ORDER == __BYTE_ORDER
#if __BYTE_ORDER == __BIG_ENDIAN
	memcpy(output,&dVal,8);
#elif __BYTE_ORDER == __LITTLE_ENDIAN
	{
		unsigned char*ci,*co;
		ci=(unsigned char*)&dVal;
		co=(unsigned char*)output;
		co[0]=ci[7];
		co[1]=ci[6];
		co[2]=ci[5];
		co[3]=ci[4];
		co[4]=ci[3];
		co[5]=ci[2];
		co[6]=ci[1];
		co[7]=ci[0];
	}
#endif
#else
#if __BYTE_ORDER == __LITTLE_ENDIAN	/* __FLOAT_WORD_ORER == __BIG_ENDIAN */
	{
		unsigned char*ci,*co;
		ci=(unsigned char*)&dVal;
		co=(unsigned char*)output;
		co[0]=ci[3];
		co[1]=ci[2];
		co[2]=ci[1];
		co[3]=ci[0];
		co[4]=ci[7];
		co[5]=ci[6];
		co[6]=ci[5];
		co[7]=ci[4];
	}
#else /* __BYTE_ORDER == __BIG_ENDIAN && __FLOAT_WORD_ORER == __LITTLE_ENDIAN */
	{
		unsigned char*ci,*co;
		ci=(unsigned char*)&dVal;
		co=(unsigned char*)output;
		co[0]=ci[4];
		co[1]=ci[5];
		co[2]=ci[6];
		co[3]=ci[7];
		co[4]=ci[0];
		co[5]=ci[1];
		co[6]=ci[2];
		co[7]=ci[3];
	}
#endif
#endif

	return output+8;
}

char*
AMF_EncodeBoolean(char *output,char *outend,int bVal)
{
	if(output+2>outend)
		return NULL;

	*output++=AMF_BOOLEAN;

	*output++=bVal?0x01:0x00;

	return output;
}

char*
AMF_EncodeNamedString(char *output,char *outend,const AVal *strName,const AVal *strValue)
{
	if(output+2+strName->av_len>outend)
		return NULL;
	output=AMF_EncodeInt16(output,outend,strName->av_len);

	memcpy(output,strName->av_val,strName->av_len);
	output+=strName->av_len;

	return AMF_EncodeString(output,outend,strValue);
}

char*
AMF_EncodeNamedNumber(char *output,char *outend,const AVal *strName,double dVal)
{
	if(output+2+strName->av_len>outend)
		return NULL;
	output=AMF_EncodeInt16(output,outend,strName->av_len);

	memcpy(output,strName->av_val,strName->av_len);
	output+=strName->av_len;

	return AMF_EncodeNumber(output,outend,dVal);
}

char*
AMF_EncodeNamedBoolean(char *output,char *outend,const AVal *strName,int bVal)
{
	if(output+2+strName->av_len>outend)
		return NULL;
	output=AMF_EncodeInt16(output,outend,strName->av_len);

	memcpy(output,strName->av_val,strName->av_len);
	output+=strName->av_len;

	return AMF_EncodeBoolean(output,outend,bVal);
}

void
AMFProp_GetName(AMFObjectProperty *prop,AVal *name)
{
	*name=prop->p_name;
}

void
AMFProp_SetName(AMFObjectProperty *prop,AVal *name)
{
	prop->p_name= *name;
}

AMFDataType
AMFProp_GetType(AMFObjectProperty *prop)
{
	return prop->p_type;
}

double
AMFProp_GetNumber(AMFObjectProperty *prop)
{
	return prop->p_vu.p_number;
}

int
AMFProp_GetBoolean(AMFObjectProperty *prop)
{
	return prop->p_vu.p_number!=0;
}

void
AMFProp_GetString(AMFObjectProperty *prop,AVal *str)
{
	*str=prop->p_vu.p_aval;
}

void
AMFProp_GetObject(AMFObjectProperty *prop,AMFObject *obj)
{
	*obj=prop->p_vu.p_object;
}

int
AMFProp_IsValid(AMFObjectProperty *prop)
{
	return prop->p_type!=AMF_INVALID;
}

char*
AMFProp_Encode(AMFObjectProperty *prop,char *pBuffer,char *pBufEnd)
{
	if(prop->p_type==AMF_INVALID)
		return NULL;

	if(prop->p_type!=AMF_NULL&&pBuffer+prop->p_name.av_len+2+1>=pBufEnd)
		return NULL;

	if(prop->p_type!=AMF_NULL&&prop->p_name.av_len)
	{
		*pBuffer++=prop->p_name.av_len>>8;
		*pBuffer++=prop->p_name.av_len&0xff;
		memcpy(pBuffer,prop->p_name.av_val,prop->p_name.av_len);
		pBuffer+=prop->p_name.av_len;
	}

	switch(prop->p_type)
	{
		case AMF_NUMBER:
			pBuffer=AMF_EncodeNumber(pBuffer,pBufEnd,prop->p_vu.p_number);
			break;

		case AMF_BOOLEAN:
			pBuffer=AMF_EncodeBoolean(pBuffer,pBufEnd,prop->p_vu.p_number!=0);
			break;

		case AMF_STRING:
			pBuffer=AMF_EncodeString(pBuffer,pBufEnd,&prop->p_vu.p_aval);
			break;

		case AMF_NULL:
			if(pBuffer+1>=pBufEnd)
				return NULL;
			*pBuffer++=AMF_NULL;
			break;

		case AMF_OBJECT:
			pBuffer=AMF_Encode(&prop->p_vu.p_object,pBuffer,pBufEnd);
			break;

		default:
			RTMP_Log(RTMP_LOGERROR,"%s, invalid type. %d",__FUNCTION__,prop->p_type);
			pBuffer=NULL;
	};

	return pBuffer;
}

#define AMF3_INTEGER_MAX	268435455
#define AMF3_INTEGER_MIN	-268435456

int
AMF3ReadInteger(const char *data,int32_t *valp)
{
	int i=0;
	int32_t val=0;

	while(i<=2)
	{
		/* handle first 3 bytes */
		if(data[i]&0x80)
		{
			/* byte used */
			val<<=7;		/* shift up */
			val|=(data[i]&0x7f);	/* add bits */
			i++;
		}
		else
			break;
	}

	if(i>2)
	{
		/* use 4th byte, all 8bits */
		val<<=8;
		val|=data[3];

		/* range check */
		if(val>AMF3_INTEGER_MAX)
			val-=(1<<29);
	}
	else
	{
		/* use 7bits of last unparsed byte (0xxxxxxx) */
		val<<=7;
		val|=data[i];
	}

	*valp=val;

	return i>2?4:i+1;
}

int
AMF3ReadString(const char *data,AVal *str)
{
	int32_t ref=0;
	int len;
	assert(str!=0);

	len=AMF3ReadInteger(data,&ref);
	data+=len;

	if((ref&0x1)==0)
	{
		/* reference: 0xxx */
		uint32_t refIndex=(ref>>1);
		RTMP_Log(RTMP_LOGDEBUG,"%s, string reference, index: %d, not supported, ignoring!",__FUNCTION__,refIndex);
		return len;
	}
	else
	{
		uint32_t nSize=(ref>>1);

		str->av_val=(char*)data;
		str->av_len=nSize;

		return len+nSize;
	}
	return len;
}

int
AMF3Prop_Decode(AMFObjectProperty *prop,const char *pBuffer,int nSize,int bDecodeName)
{
	int nOriginalSize=nSize;
	AMF3DataType type;

	prop->p_name.av_len=0;
	prop->p_name.av_val=NULL;

	if(nSize==0||!pBuffer)
	{
		RTMP_Log(RTMP_LOGDEBUG,"empty buffer/no buffer pointer!");
		return -1;
	}

	/* decode name */
	if(bDecodeName)
	{
		AVal name;
		int nRes=AMF3ReadString(pBuffer,&name);

		if(name.av_len<=0)
			return nRes;

		prop->p_name=name;
		pBuffer+=nRes;
		nSize-=nRes;
	}

	/* decode */
	type= *pBuffer++;
	nSize--;

	switch(type)
	{
		case AMF3_UNDEFINED:
		case AMF3_NULL:
			prop->p_type=AMF_NULL;
			break;
		case AMF3_FALSE:
			prop->p_type=AMF_BOOLEAN;
			prop->p_vu.p_number=0.0;
			break;
		case AMF3_TRUE:
			prop->p_type=AMF_BOOLEAN;
			prop->p_vu.p_number=1.0;
			break;
		case AMF3_INTEGER:
			{
				int32_t res=0;
				int len=AMF3ReadInteger(pBuffer,&res);
				prop->p_vu.p_number=(double)res;
				prop->p_type=AMF_NUMBER;
				nSize-=len;
				break;
			}
		case AMF3_DOUBLE:
			if(nSize<8)
				return -1;
			prop->p_vu.p_number=AMF_DecodeNumber(pBuffer);
			prop->p_type=AMF_NUMBER;
			nSize-=8;
			break;
		case AMF3_STRING:
		case AMF3_XML_DOC:
		case AMF3_XML:
			{
				int len=AMF3ReadString(pBuffer,&prop->p_vu.p_aval);
				prop->p_type=AMF_STRING;
				nSize-=len;
				break;
			}
		case AMF3_DATE:
			{
				int32_t res=0;
				int len=AMF3ReadInteger(pBuffer,&res);

				nSize-=len;
				pBuffer+=len;

				if((res&0x1)==0)
				{
					/* reference */
					uint32_t nIndex=(res>>1);
					RTMP_Log(RTMP_LOGDEBUG,"AMF3_DATE reference: %d, not supported!",nIndex);
				}
				else
				{
					if(nSize<8)
						return -1;

					prop->p_vu.p_number=AMF_DecodeNumber(pBuffer);
					nSize-=8;
					prop->p_type=AMF_NUMBER;
				}
				break;
			}
		case AMF3_OBJECT:
			{
				int nRes=AMF3_Decode(&prop->p_vu.p_object,pBuffer,nSize,TRUE);
				if(nRes==-1)
					return -1;
				nSize-=nRes;
				prop->p_type=AMF_OBJECT;
				break;
			}
		case AMF3_ARRAY:
		case AMF3_BYTE_ARRAY:
		default:
			RTMP_Log(RTMP_LOGDEBUG,"%s - AMF3 unknown/unsupported datatype 0x%02x, @%p",__FUNCTION__,(unsigned char)(*pBuffer),pBuffer);
			return -1;
	}

	return nOriginalSize-nSize;
}

int
AMFProp_Decode(AMFObjectProperty *prop,const char *pBuffer,int nSize,int bDecodeName)
{
	int nOriginalSize=nSize;
	int nRes;

	prop->p_name.av_len=0;
	prop->p_name.av_val=NULL;

	if(nSize==0||!pBuffer)
	{
		RTMP_Log(RTMP_LOGDEBUG,"%s: Empty buffer/no buffer pointer!",__FUNCTION__);
		return -1;
	}

	if(bDecodeName&&nSize<4)
	{
		/* at least name (length + at least 1 byte) and 1 byte of data */
		RTMP_Log(RTMP_LOGDEBUG,"%s: Not enough data for decoding with name, less than 4 bytes!",__FUNCTION__);
		return -1;
	}

	if(bDecodeName)
	{
		unsigned short nNameSize=AMF_DecodeInt16(pBuffer);
		if(nNameSize>nSize-2)
		{
			RTMP_Log(RTMP_LOGDEBUG,"%s: Name size out of range: namesize (%d) > len (%d) - 2",__FUNCTION__,nNameSize,nSize);
			return -1;
		}

		AMF_DecodeString(pBuffer,&prop->p_name);
		nSize-=2+nNameSize;
		pBuffer+=2+nNameSize;
	}

	if(nSize==0)
		return -1;

	nSize--;

	prop->p_type= *pBuffer++;
	switch(prop->p_type)
	{
		case AMF_NUMBER:
			if(nSize<8)
				return -1;
			prop->p_vu.p_number=AMF_DecodeNumber(pBuffer);
			nSize-=8;
			break;
		case AMF_BOOLEAN:
			if(nSize<1)
				return -1;
			prop->p_vu.p_number=(double)AMF_DecodeBoolean(pBuffer);
			nSize--;
			break;
		case AMF_STRING:
			{
				unsigned short nStringSize=AMF_DecodeInt16(pBuffer);

				if(nSize<(long)nStringSize+2)
					return -1;
				AMF_DecodeString(pBuffer,&prop->p_vu.p_aval);
				nSize-=(2+nStringSize);
				break;
			}
		case AMF_OBJECT:
			{
				int nRes=AMF_Decode(&prop->p_vu.p_object,pBuffer,nSize,TRUE);
				if(nRes==-1)
					return -1;
				nSize-=nRes;
				break;
			}
		case AMF_MOVIECLIP:
			{
				RTMP_Log(RTMP_LOGERROR,"AMF_MOVIECLIP reserved!");
				return -1;
				break;
			}
		case AMF_NULL:
		case AMF_UNDEFINED:
		case AMF_UNSUPPORTED:
			prop->p_type=AMF_NULL;
			break;
		case AMF_REFERENCE:
			{
				RTMP_Log(RTMP_LOGERROR,"AMF_REFERENCE not supported!");
				return -1;
				break;
			}
		case AMF_ECMA_ARRAY:
			{
				nSize-=4;

				/* next comes the rest, mixed array has a final 0x000009 mark and names, so its an object */
				nRes=AMF_Decode(&prop->p_vu.p_object,pBuffer+4,nSize,TRUE);
				if(nRes==-1)
					return -1;
				nSize-=nRes;
				prop->p_type=AMF_OBJECT;
				break;
			}
		case AMF_OBJECT_END:
			{
				return -1;
				break;
			}
		case AMF_STRICT_ARRAY:
			{
				unsigned int nArrayLen=AMF_DecodeInt32(pBuffer);
				nSize-=4;

				nRes=AMF_DecodeArray(&prop->p_vu.p_object,pBuffer+4,nSize,nArrayLen,FALSE);
				if(nRes==-1)
					return -1;
				nSize-=nRes;
				prop->p_type=AMF_OBJECT;
				break;
			}
		case AMF_DATE:
			{
				RTMP_Log(RTMP_LOGDEBUG,"AMF_DATE");

				if(nSize<10)
					return -1;

				prop->p_vu.p_number=AMF_DecodeNumber(pBuffer);
				prop->p_UTCoffset=AMF_DecodeInt16(pBuffer+8);

				nSize-=10;
				break;
			}
		case AMF_LONG_STRING:
		case AMF_XML_DOC:
			{
				unsigned int nStringSize=AMF_DecodeInt32(pBuffer);
				if(nSize<(long)nStringSize+4)
					return -1;
				AMF_DecodeLongString(pBuffer,&prop->p_vu.p_aval);
				nSize-=(4+nStringSize);
				if(prop->p_type==AMF_LONG_STRING)
					prop->p_type=AMF_STRING;
				break;
			}
		case AMF_RECORDSET:
			{
				RTMP_Log(RTMP_LOGERROR,"AMF_RECORDSET reserved!");
				return -1;
				break;
			}
		case AMF_TYPED_OBJECT:
			{
				RTMP_Log(RTMP_LOGERROR,"AMF_TYPED_OBJECT not supported!");
				return -1;
				break;
			}
		case AMF_AVMPLUS:
			{
				int nRes=AMF3_Decode(&prop->p_vu.p_object,pBuffer,nSize,TRUE);
				if(nRes==-1)
					return -1;
				nSize-=nRes;
				prop->p_type=AMF_OBJECT;
				break;
			}
		default:
			RTMP_Log(RTMP_LOGDEBUG,"%s - unknown datatype 0x%02x, @%p",__FUNCTION__,prop->p_type,pBuffer-1);
			return -1;
	}

	return nOriginalSize-nSize;
}

void
AMFProp_Dump(AMFObjectProperty *prop)
{
	char strRes[256];
	char str[256];
	AVal name;

	if(prop->p_type==AMF_INVALID)
	{
		RTMP_Log(RTMP_LOGDEBUG,"Property: INVALID");
		return;
	}

	if(prop->p_type==AMF_NULL)
	{
		RTMP_Log(RTMP_LOGDEBUG,"Property: NULL");
		return;
	}

	if(prop->p_name.av_len)
		name=prop->p_name;
	else
	{
		name.av_val="no-name.";
		name.av_len=sizeof("no-name.")-1;
	}
	if(name.av_len>18)
		name.av_len=18;

	snprintf(strRes,255,"Name: %18.*s, ",name.av_len,name.av_val);

	if(prop->p_type==AMF_OBJECT)
	{
		RTMP_Log(RTMP_LOGDEBUG,"Property: <%sOBJECT>",strRes);
		AMF_Dump(&prop->p_vu.p_object);
		return;
	}

	switch(prop->p_type)
	{
		case AMF_NUMBER:
			snprintf(str,255,"NUMBER:\t%.2f",prop->p_vu.p_number);
			break;
		case AMF_BOOLEAN:
			snprintf(str,255,"BOOLEAN:\t%s",prop->p_vu.p_number!=0.0?"TRUE":"FALSE");
			break;
		case AMF_STRING:
			snprintf(str,255,"STRING:\t%.*s",prop->p_vu.p_aval.av_len,prop->p_vu.p_aval.av_val);
			break;
		case AMF_DATE:
			snprintf(str,255,"DATE:\ttimestamp: %.2f, UTC offset: %d",prop->p_vu.p_number,prop->p_UTCoffset);
			break;
		default:
			snprintf(str,255,"INVALID TYPE 0x%02x",(unsigned char)prop->p_type);
	}

	RTMP_Log(RTMP_LOGDEBUG,"Property: <%s%s>",strRes,str);
}

void
AMFProp_Reset(AMFObjectProperty *prop)
{
	if(prop->p_type==AMF_OBJECT)
		AMF_Reset(&prop->p_vu.p_object);
	else
	{
		prop->p_vu.p_aval.av_len=0;
		prop->p_vu.p_aval.av_val=NULL;
	}
	prop->p_type=AMF_INVALID;
}

/* AMFObject */

char*
AMF_Encode(AMFObject *obj,char *pBuffer,char *pBufEnd)
{
	int i;

	if(pBuffer+4>=pBufEnd)
		return NULL;

	*pBuffer++=AMF_OBJECT;

	for(i=0;i<obj->o_num;i++)
	{
		char *res=AMFProp_Encode(&obj->o_props[i],pBuffer,pBufEnd);
		if(res==NULL)
		{
			RTMP_Log(RTMP_LOGERROR,"AMF_Encode - failed to encode property in index %d",i);
			break;
		}
		else
			pBuffer=res;
	}

	if(pBuffer+3>=pBufEnd)
		return NULL;			/* no room for the end marker */

	pBuffer=AMF_EncodeInt24(pBuffer,pBufEnd,AMF_OBJECT_END);

	return pBuffer;
}

int
AMF_DecodeArray(AMFObject *obj,const char *pBuffer,int nSize,int nArrayLen,int bDecodeName)
{
	int nOriginalSize=nSize;
	int bError=FALSE;

	obj->o_num=0;
	obj->o_props=NULL;
	while(nArrayLen>0)
	{
		AMFObjectProperty prop;
		int nRes;
		nArrayLen--;

		nRes=AMFProp_Decode(&prop,pBuffer,nSize,bDecodeName);
		if(nRes==-1)
			bError=TRUE;
		else
		{
			nSize-=nRes;
			pBuffer+=nRes;
			AMF_AddProp(obj,&prop);
		}
	}
	if(bError)
		return -1;

	return nOriginalSize-nSize;
}

int
AMF3_Decode(AMFObject *obj,const char *pBuffer,int nSize,int bAMFData)
{
	int nOriginalSize=nSize;
	int32_t ref;
	int len;

	obj->o_num=0;
	obj->o_props=NULL;
	if(bAMFData)
	{
		if(*pBuffer!=AMF3_OBJECT)
			RTMP_Log(RTMP_LOGERROR,"AMF3 Object encapsulated in AMF stream does not start with AMF3_OBJECT!");
		pBuffer++;
		nSize--;
	}

	ref=0;
	len=AMF3ReadInteger(pBuffer,&ref);
	pBuffer+=len;
	nSize-=len;

	if((ref&1)==0)
	{
		/* object reference, 0xxx */
		uint32_t objectIndex=(ref>>1);

		RTMP_Log(RTMP_LOGDEBUG,"Object reference, index: %d",objectIndex);
	}
	else				/* object instance */
	{
		int32_t classRef=(ref>>1);

		AMF3ClassDef cd={{0, 0}};
		AMFObjectProperty prop;

		if((classRef&0x1)==0)
		{
			/* class reference */
			uint32_t classIndex=(classRef>>1);
			RTMP_Log(RTMP_LOGDEBUG,"Class reference: %d",classIndex);
		}
		else
		{
			int32_t classExtRef=(classRef>>1);
			int i;

			cd.cd_externalizable=(classExtRef&0x1)==1;
			cd.cd_dynamic=((classExtRef>>1)&0x1)==1;

			cd.cd_num=classExtRef>>2;

			/* class name */

			len=AMF3ReadString(pBuffer,&cd.cd_name);
			nSize-=len;
			pBuffer+=len;

			/*std::string str = className; */

			RTMP_Log(RTMP_LOGDEBUG,"Class name: %s, externalizable: %d, dynamic: %d, classMembers: %d",cd.cd_name.av_val,cd.cd_externalizable,cd.cd_dynamic,cd.cd_num);

			for(i=0;i<cd.cd_num;i++)
			{
				AVal memberName;
				len=AMF3ReadString(pBuffer,&memberName);
				RTMP_Log(RTMP_LOGDEBUG,"Member: %s",memberName.av_val);
				AMF3CD_AddProp(&cd,&memberName);
				nSize-=len;
				pBuffer+=len;
			}
		}

		/* add as referencable object */

		if(cd.cd_externalizable)
		{
			int nRes;
			AVal name=AVC("DEFAULT_ATTRIBUTE");

			RTMP_Log(RTMP_LOGDEBUG,"Externalizable, TODO check");

			nRes=AMF3Prop_Decode(&prop,pBuffer,nSize,FALSE);
			if(nRes==-1)
				RTMP_Log(RTMP_LOGDEBUG,"%s, failed to decode AMF3 property!",__FUNCTION__);
			else
			{
				nSize-=nRes;
				pBuffer+=nRes;
			}

			AMFProp_SetName(&prop,&name);
			AMF_AddProp(obj,&prop);
		}
		else
		{
			int nRes,i;
			for(i=0;i<cd.cd_num;i++)	/* non-dynamic */
			{
				nRes=AMF3Prop_Decode(&prop,pBuffer,nSize,FALSE);
				if(nRes==-1)
					RTMP_Log(RTMP_LOGDEBUG,"%s, failed to decode AMF3 property!",__FUNCTION__);

				AMFProp_SetName(&prop,AMF3CD_GetProp(&cd,i));
				AMF_AddProp(obj,&prop);

				pBuffer+=nRes;
				nSize-=nRes;
			}
			if(cd.cd_dynamic)
			{
				int len=0;

				do
				{
					nRes=AMF3Prop_Decode(&prop,pBuffer,nSize,TRUE);
					AMF_AddProp(obj,&prop);

					pBuffer+=nRes;
					nSize-=nRes;

					len=prop.p_name.av_len;
				}
				while(len>0);
			}
		}
		RTMP_Log(RTMP_LOGDEBUG,"class object!");
	}
	return nOriginalSize-nSize;
}

int
AMF_Decode(AMFObject *obj,const char *pBuffer,int nSize,int bDecodeName)
{
	int nOriginalSize=nSize;
	int bError=FALSE;		/* if there is an error while decoding - try to at least find the end mark AMF_OBJECT_END */

	obj->o_num=0;
	obj->o_props=NULL;
	while(nSize>0)
	{
		AMFObjectProperty prop;
		int nRes;

		if(nSize>=3&&AMF_DecodeInt24(pBuffer)==AMF_OBJECT_END)
		{
			nSize-=3;
			bError=FALSE;
			break;
		}

		if(bError)
		{
			RTMP_Log(RTMP_LOGERROR,"DECODING ERROR, IGNORING BYTES UNTIL NEXT KNOWN PATTERN!");
			nSize--;
			pBuffer++;
			continue;
		}

		nRes=AMFProp_Decode(&prop,pBuffer,nSize,bDecodeName);
		if(nRes==-1)
			bError=TRUE;
		else
		{
			nSize-=nRes;
			pBuffer+=nRes;
			AMF_AddProp(obj,&prop);
		}
	}

	if(bError)
		return -1;

	return nOriginalSize-nSize;
}

void
AMF_AddProp(AMFObject *obj,const AMFObjectProperty *prop)
{
	if(!(obj->o_num&0x0f))
		obj->o_props=realloc(obj->o_props,(obj->o_num+16)*sizeof(AMFObjectProperty));
	memcpy(&obj->o_props[obj->o_num++],prop,sizeof(AMFObjectProperty));
}

int
AMF_CountProp(AMFObject *obj)
{
	return obj->o_num;
}

AMFObjectProperty*
AMF_GetProp(AMFObject *obj,const AVal *name,int nIndex)
{
	if(nIndex>=0)
	{
		if(nIndex<=obj->o_num)
			return &obj->o_props[nIndex];
	}
	else
	{
		int n;
		for(n=0;n<obj->o_num;n++)
		{
			if(AVMATCH(&obj->o_props[n].p_name,name))
				return &obj->o_props[n];
		}
	}

	return (AMFObjectProperty*)&AMFProp_Invalid;
}

void
AMF_Dump(AMFObject *obj)
{
	int n;
	RTMP_Log(RTMP_LOGDEBUG,"(object begin)");
	for(n=0;n<obj->o_num;n++)
		AMFProp_Dump(&obj->o_props[n]);
	RTMP_Log(RTMP_LOGDEBUG,"(object end)");
}

void
AMF_Reset(AMFObject *obj)
{
	int n;
	for(n=0;n<obj->o_num;n++)
		AMFProp_Reset(&obj->o_props[n]);
	free(obj->o_props);
	obj->o_props=NULL;
	obj->o_num=0;
}


/* AMF3ClassDefinition */

void
AMF3CD_AddProp(AMF3ClassDef *cd,AVal *prop)
{
	if(!(cd->cd_num&0x0f))
		cd->cd_props=realloc(cd->cd_props,(cd->cd_num+16)*sizeof(AVal));
	cd->cd_props[cd->cd_num++]= *prop;
}

AVal*
AMF3CD_GetProp(AMF3ClassDef *cd,int nIndex)
{
	if(nIndex>=cd->cd_num)
		return (AVal*)&AV_empty;
	return &cd->cd_props[nIndex];
}
